import java.io.*;
import java.util.*;
import java.lang.*;

public class Container implements Serializable
//Done so that we can pass Containers back and forth between Agents, amirite?
{
  protected ContainerMessage [] msgRX;
  protected ContainerMessage [] msgTX;
  protected Map<String,ContainerList> BreechedContainers;
  protected Map<String,ContainerList> NeighborContainers;
  protected String GlobalLocation;
  protected String name;
  protected boolean Breeched;
  protected boolean Lost;
  protected int [] location = new int[3];
  protected int lostCount;
  protected int RXindex;
  protected int state;
  protected int TXindex;
  protected long CLOCK_TIME;
  protected long CYCLE_LENGTH;
  protected long RECEIVE_TIME_1;
  protected long RECEIVE_TIME_2;
  protected long SEND_TIME;
  protected long SLEEP_TIME;

  public Container(String name, int x, int y, int z, int state,
      long CLOCK_TIME, long CYCLE_LENGTH, boolean breeched, boolean lost)
  {
    msgRX = new ContainerMessage[50];
    msgTX = new ContainerMessage[50];
    BreechedContainers = new HashMap<String,ContainerList>();
    NeighborContainers = new HashMap<String,ContainerList>();
    GlobalLocation = "";
    this.name = name;
    this.Breeched = breeched;
    this.Lost = Lost;
    location[0] = x;
    location[1] = y;
    location[2] = z;
    lostCount = 0;
    this.state = state;
    this.CLOCK_TIME = CLOCK_TIME;
    this.CYCLE_LENGTH = CYCLE_LENGTH;
    timeAmounts(CYCLE_LENGTH);
    TXindex = -1;
    RXindex = -1;
  }

  public Container(String TokenizedContainer, String delim, String subdelim)
  {
    //Tokenized Crates should be in the following form using "varname(type)"|
    // x_location(int)|y_location(int)|z_location(int)|state(int)|
    // CLOCK_TIME(long)|RXlength(int)|RXindex(int)|
    // ReceivedMessage1(String)|..|ReceivedMessageN(String)|
    // TXlength(int)|TXindex(int)|SendMessage1(String)|..|
    // SendMessageN(String)|CYCLE_LENGTH(long)|
    // NClength(int)|NeighborContainer1(ContainerList)|..|
    // NeighborContainerN(ContainerList)|BClength(int)|
    // BreechedContainer1(ContainerList)|..|BreechedContainerN(ContainerList)
    StringTokenizer st  = new StringTokenizer(TokenizedContainer,delim);
    this.location[0]    = Integer.parseInt(st.nextToken());
    this.location[1]    = Integer.parseInt(st.nextToken());
    this.location[2]    = Integer.parseInt(st.nextToken());
    this.state          = Integer.parseInt(st.nextToken());
    this.CLOCK_TIME     = Long.parseLong(st.nextToken());
    int RXlength        = Integer.parseInt(st.nextToken());
    this.msgRX          = new ContainerMessage[RXlength];
    this.RXindex        = Integer.parseInt(st.nextToken());
    int index = 0;
    String token;
    while (index <= this.RXindex)
    {
      // This will take a Tokenized String representing each Container Message
      // They will be delimited with a colon ':' and the ContainerMessage will
      // have a constructor to handle Tokenized Strings, much like this class
      token = st.nextToken();
      if (!token.equals("null"))
        this.msgRX[index] = new ContainerMessage(token,subdelim);
      else
        this.msgRX[index] = null;
      index++;
    }
    int TXlength        = Integer.parseInt(st.nextToken());
    this.msgTX          = new ContainerMessage[TXlength];
    this.TXindex        = Integer.parseInt(st.nextToken());
    index               = 0;
    while (index <= this.TXindex)
    {
      // This does exactly what msgRX did, but for a different array and a
      // different list.
      token = st.nextToken();
      if (!token.equals("null"))
        this.msgTX[index] = new ContainerMessage(token, subdelim);
      else
        this.msgTX[index] = null;
      index++;
    }
    this.CYCLE_LENGTH   = Long.parseLong(st.nextToken());
    timeAmounts(this.CYCLE_LENGTH);
    
    int NClength = Integer.parseInt(st.nextToken());
    NeighborContainers  = new HashMap<String, ContainerList>();

    index = 0;
    while (index <= NClength)
    {
      token = st.nextToken();
      this.NeighborContainers.put(token.substring(0,token.indexOf(subdelim.
              charAt(0))), new ContainerList(token));
      // This creates a substring that grabs the name of the Container
      index++;
    }


    int BClength = Integer.parseInt(st.nextToken());
    BreechedContainers = new HashMap<String, ContainerList>();

    index = 0;
    while (index <= BClength)
    {
      token = st.nextToken();
      this.BreechedContainers.put(token.substring(0,token.indexOf(subdelim.
              charAt(0))), new ContainerList(token,subdelim));
      index++;
    }
  }


/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 ~                              END CONSTRUCTORS                             ~
 ~                            BEGIN NORMAL FUNCTIONS                         ~
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/

  
  public void timeAmounts(long CYCLE_LENGTH)
  {
    // Each value denotes the time that the state should START AT.
    SLEEP_TIME     = 0;
    RECEIVE_TIME_1 = Math.round(.67*CYCLE_LENGTH);
    SEND_TIME      = Math.round(.11*CYCLE_LENGTH);
    RECEIVE_TIME_2 = Math.round(.11*CYCLE_LENGTH);
    // You can find out what state you're in by CLOCK_CYCLE % CLOCK_LENGTH
    // This will return a value, and if this value is greather, than say,
    // SEND_TIME and less than RECEIVE_TIME_2, it is in SEND state
  }

  public String toString()
  {
    String delim = ":";
    String subdelim = "|";
    String message = "\n\n\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n\nNAME: " +
      this.name + "\n" + 
      "X = \t" + this.location[0] + "\n" + 
      "Y = \t" + this.location[1] + "\n" +
      "Z = \t" + this.location[2] + "\n" + 
      "state = \t" + this.state + "\n" +
      "CLOCK = \t" + this.CLOCK_TIME + "\n" + 
      "\n-------------------------\nInbox(" + (this.RXindex + 1) + ")\n";
    for (int i = 0; i < msgRX.length; i++)
    {
      if (msgRX[i] != null)
        message += msgRX[i].toString() + "\n";
      
    }

    message += "\nOutbox(" + (this.TXindex + 1) + ")\n";
    
    for (int i = 0; i < msgTX.length; i++)
    {
      if (msgTX[i] != null)
        message += msgTX[i].toString() + "\n";
    }

    message += "-------------------------\nCYCLE = \t" + this.CYCLE_LENGTH + 
      "\n" + "Neighbors(" + NeighborContainers.size() + ")\n" +
      "Name\tLC\tBreeched\n";
    Iterator it = NeighborContainers.values().iterator();
    ContainerList cl;

    while(it.hasNext())
    {
      cl = (ContainerList)it.next();
      message += cl.toString() + "\n";
    }

    message += "Breeched(" + BreechedContainers.size() + ")\n" +
      "Name\tLC\tBreeched\n";
    it = BreechedContainers.values().iterator();

    while(it.hasNext())
    {
      cl = (ContainerList)it.next();
      message += cl.toString() + "\n";
    }

    message += "\n\n\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n\n";

    return message;
    
  }

  public String toMessage(String delim)
  {
    // Tokenized Crates should be in the following form using "varname(type)"|
    // x_location(int)|y_location(int)|z_location(int)|state(int)|
    // CLOCK_TIME(long)|RXlength(int)|RXindex(int)|
    // ReceivedMessage1(String)|..|ReceivedMessageN(String)|
    // TXlength(int)|TXindex(int)|SendMessage1(String)|..|
    // SendMessageN(String)|CYCLE_LENGTH(long)|
    // NClength(int)|NeighborContainer1(ContainerList)|..|
    // NeighborContainerN(ContainerList)|BClength(int)|
    // BreechedContainer1(ContainerList)|..|BreechedContainerN(ContainerList)
    String message = this.location[0] + delim + this.location[1] + delim +
      this.location[2] + delim + this.state + delim + this.CLOCK_TIME + 
      delim + this.msgRX.length + delim;
    String subdelim;
    if (delim.equals(":"))
      subdelim = "|";
    else
      subdelim = ":";
    
    Iterator it;

    for (int i = 0; i < msgRX.length; i++)
    {
      if (msgRX[i] == null)
        message += "null" + delim;
      else
        message += msgRX[i].toMessage(subdelim) + delim;
      
    }

    message += this.msgTX.length + delim;
    
    for (int i = 0; i < msgTX.length; i++)
    {
      if (msgTX[i] == null)
        message += "null" + delim;
      else
        message += msgTX[i].toMessage(subdelim) + delim;
    }

    message += this.CYCLE_LENGTH + delim + NeighborContainers.size() + delim;
    
    it = NeighborContainers.values().iterator();
    ContainerList cl;
    while(it.hasNext())
    {
      cl = (ContainerList)it.next();
      message += cl.toMessage(subdelim) + delim;
    }

    message += BreechedContainers.size() + delim;
    
    it = BreechedContainers.values().iterator();

    while(it.hasNext())
    {
      cl = (ContainerList)it.next();
      message += cl.toMessage(subdelim) + delim;
    }
    return message;

  }
  
  public void  receiveMessage(String msg)
  {
    this.RXindex++;
    msgRX[RXindex] = new ContainerMessage(msg, "\\%");
  }

  public void receiveMessage(ContainerMessage msg)
  {
    this.RXindex++;
    msgRX[RXindex] = new ContainerMessage(msg);
  }

  public ContainerMessage sendMessage()
  {
    ContainerMessage message = new ContainerMessage(msgTX[TXindex]);
    msgTX[TXindex] = null;
    this.TXindex--;
    return message;
  }

  public void addMessageToSend(ContainerMessage message)
  {
     this.TXindex++;
     msgTX[TXindex] = new ContainerMessage(message);
  }

  public void processEnvAndState()
  {
    // The steps that the Container needs to do to see if everything is well.
    // 1) Check the inbox to see if there are any new messages, and process
    // each one. Set counts to zero of nodes you know are still active.
    // Set breeched counters as necessary.
    //
    // 2) Look through lists to see if anyone is lost or breeched. Add messages
    // to queue as need be. Remember that Node 0 of neighbors is reserved for
    // yourself.
    //
    // 3) Check own breeched status, add to list as necessary.
    //
    // 4) Are you lost?
    //
    // 5) is your outbox still 0? send a timestamp message then, just so others
    // know you are still active.
    this.lostCount++;
    //Increase your lostCount until you've received a message
    int i = 0;
    while (i < msgRX.length && msgRX[i] != null)
    {
      Iterator it = NeighborContainers.values().iterator();
      while (it.hasNext())
      {
        ContainerList cl = (ContainerList)it.next();
        cl.incrementLostCount();
      }
      // I just incremented the lost count of each item to ensure we know
      // when a container is lost.
      //
      // This next part basically says that since you got a message from someone,
      // they're not lost, so remove that lost count.
      String sender = msgRX[i].getSender();
      // It's fairly fast, because it's hashed (just as fast as sets)
      // should be based on the Container's name(string).
      if (NeighborContainers.containsKey(sender))
        NeighborContainers.get(sender).resetLostCount();
      else
        NeighborContainers.put(sender, new ContainerList(sender,0,false));

      // Is the container breeched?  if so, add him to the breeched list
      // and update the neighborcontainers.
      if (msgRX[i].getContent().charAt(0) == 'B' ||
          msgRX[i].getContent().charAt(0) == 'b')
      {
        String [] sp = msgRX[i].getContent().split("\\&");
        if (sp[1].equals(sp[2]))
        {
          if (NeighborContainers.containsKey(sender))
            NeighborContainers.get(sender).setBreeched(true);

          if (BreechedContainers.containsKey(sender))
            NeighborContainers.put(sender, new ContainerList(sender,0,true));
        }
        if (BreechedContainers.containsKey(sp[2]))
        {
          NeighborContainers.get(sp[2]).setBreeched(true);
        }
        else
          NeighborContainers.put(sp[2], new ContainerList(sp[2],0,true));
        
      }
      
      // Send out messages about all the lost containers you know, if you
      // yourself are not lost.

      it = NeighborContainers.keySet().iterator();
      while (it.hasNext())
      {
        ContainerList cl = NeighborContainers.get(it.next());
        if (cl.getLostCount() >= 4 && this.lostCount < 4)
        {
          this.addMessageToSend(new ContainerMessage(ContainerMessage.INFORM,
              this.name, null, "Container-message-ontology", "English",
              null, "L&" + this.name + "&" + cl.Name + "&" +
              this.CLOCK_TIME));
        }
      }

      // Send out messages about all the breeched containers you know of.

      it = BreechedContainers.keySet().iterator();
      while (it.hasNext())
      {
        ContainerList cl = BreechedContainers.get(it.next());
        if (cl.getBreeched())
        {
          this.addMessageToSend(new ContainerMessage(ContainerMessage.INFORM,
              this.name, null, "Container-message-ontology", "English",
              null, "B&" + this.name + "&" + cl.Name + "&" + this.CLOCK_TIME));
        }
        else
          it.remove();
      }
      msgRX[i] = null;
      RXindex--;
      this.lostCount = 0;
      this.Lost = false;
    }

    if (this.Breeched)
      this.addMessageToSend(new ContainerMessage(ContainerMessage.INFORM,
              this.name, null, "Container-message-ontology", "English",
              null, "B&" + this.name + "&" + this.name + "&" +
              this.CLOCK_TIME));

    if (this.lostCount >= 4 && !this.Lost)
    {
      //Go into lost mode
      this.CYCLE_LENGTH/= 2;
      timeAmounts(CYCLE_LENGTH);
      this.Lost = true;
    }

    if (TXindex < 0)
    {
      this.addMessageToSend(new ContainerMessage(ContainerMessage.INFORM,
              this.name, null, "Container-message-ontology", "English",
              null, "T&" + this.name + "&" + this.name + "&" +
              this.CLOCK_TIME));
    }

  }

  
  protected void incrementClock()
  {
    this.CLOCK_TIME++;
  }

  protected void ATTACK_lost(boolean lost)
  {
    this.Lost = lost;
  }

  protected void ATTACK_breech(boolean breech)
  {
    this.Breeched = breech;
  }
}
  


